/*
 * Copyright 2012-2021 the original author or authors.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      https://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package org.springframework.boot.context.config;

import org.springframework.boot.origin.Origin;
import org.springframework.boot.origin.OriginProvider;
import org.springframework.util.StringUtils;

/**
 * 用户指定的位置，可以通过 {@link ConfigDataLocationResolver} 解析为一个或多个
 * {@link ConfigDataResource 配置数据资源}。{@link ConfigDataLocation} 是对一个 {@link String}
 * 值的简单封装。该值的具体格式取决于底层技术，通常是类似 URL 的语法，由前缀和路径组成，例如 {@code crypt:somehost/somepath}。
 * <p>
 * 位置可以是必需的，也可以是 {@link #isOptional() 可选的}。可选位置以 {@code optional:} 作为前缀。
 *
 * @author Phillip Webb
 * @since 2.4.0
 */
public final class ConfigDataLocation implements OriginProvider {

	/**
	 * 用于表示 {@link ConfigDataResource} 可选的前缀。
	 */
	public static final String OPTIONAL_PREFIX = "optional:";

	private final boolean optional;

	private final String value;

	private final Origin origin;

	private ConfigDataLocation(boolean optional, String value, Origin origin) {
		this.value = value;
		this.optional = optional;
		this.origin = origin;
	}

	/**
	 * 返回该位置是否为可选，且应忽略 {@link ConfigDataNotFoundException} 异常。
	 * @return 如果该位置是可选的，则返回 true
	 */
	public boolean isOptional() {
		return this.optional;
	}

	/**
	 * 返回位置的值（总是排除任何用户指定的 {@code optional:} 前缀）。
	 * @return 位置的值
	 */
	public String getValue() {
		return this.value;
	}

	/**
	 * 判断 {@link #getValue()} 是否具有指定的前缀。
	 * @param prefix 要检查的前缀
	 * @return 如果值具有该前缀，则返回 true
	 */
	public boolean hasPrefix(String prefix) {
		return this.value.startsWith(prefix);
	}

	/**
	 * 返回去除指定前缀后的 {@link #getValue()}。如果位置不包含该前缀，则返回原始的 {@link #getValue()}。
	 * @param prefix 要去除的前缀
	 * @return 去除前缀后的值
	 */
	public String getNonPrefixedValue(String prefix) {
		if (hasPrefix(prefix)) {
			return this.value.substring(prefix.length());
		}
		return this.value;
	}

	@Override
	public Origin getOrigin() {
		return this.origin;
	}

	/**
	 * 返回一个由 {@link ConfigDataLocation} 元素组成的数组，该数组是通过以 {@code ";"} 为分隔符拆分当前
	 * {@link ConfigDataLocation} 得到的。
	 * @return 拆分后的多个位置数组
	 * @since 2.4.7
	 */
	public ConfigDataLocation[] split() {
		return split(";");
	}

	/**
	 * 返回一个由 {@link ConfigDataLocation} 元素组成的数组，该数组是通过以指定的分隔符拆分当前
	 * {@link ConfigDataLocation} 得到的。
	 * @param delimiter 用于拆分的分隔符
	 * @return 拆分后的多个位置数组
	 * @since 2.4.7
	 */
	public ConfigDataLocation[] split(String delimiter) {
		String[] values = StringUtils.delimitedListToStringArray(toString(), delimiter);
		ConfigDataLocation[] result = new ConfigDataLocation[values.length];
		for (int i = 0; i < values.length; i++) {
			result[i] = of(values[i]).withOrigin(getOrigin());
		}
		return result;
	}

	@Override
	public boolean equals(Object obj) {
		if (this == obj) {
			return true;
		}
		if (obj == null || getClass() != obj.getClass()) {
			return false;
		}
		ConfigDataLocation other = (ConfigDataLocation) obj;
		return this.value.equals(other.value);
	}

	@Override
	public int hashCode() {
		return this.value.hashCode();
	}

	@Override
	public String toString() {
		return (!this.optional) ? this.value : OPTIONAL_PREFIX + this.value;
	}

	/**
	 * 使用指定的 {@link Origin} 创建一个新的 {@link ConfigDataLocation}。
	 * @param origin 要设置的来源
	 * @return 一个新的 {@link ConfigDataLocation} 实例
	 */
	ConfigDataLocation withOrigin(Origin origin) {
		return new ConfigDataLocation(this.optional, this.value, origin);
	}

	/**
	 * 工厂方法，根据字符串创建一个新的 {@link ConfigDataLocation}。
	 * @param location 位置字符串
	 * @return {@link ConfigDataLocation} 实例，如果未提供位置则返回 {@code null}
	 */
	public static ConfigDataLocation of(String location) {
		boolean optional = location != null && location.startsWith(OPTIONAL_PREFIX);
		String value = (!optional) ? location : location.substring(OPTIONAL_PREFIX.length());
		if (!StringUtils.hasText(value)) {
			return null;
		}
		return new ConfigDataLocation(optional, value, null);
	}

}
